﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Reflection;
using System.IO.Compression;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.Win32;
using QTPlugin;
using Kawanon.QtHoney;

namespace Kawanon.Usability
{
	public static class GestureSettings
	{
		private const int ARCHIVE_VERSION = 1;

		[Serializable]
		internal struct ExpressionArchiveHeader
		{
			public int Version { get; set; }
			public int ObjectCount { get; set; }
		}

		// QTTabBar でロード時に BinaryFormatter がアセンブリを特定できない問題の対策（GACがらみ？）
		class WorkaroundSerializationBinder : SerializationBinder
		{
			public override Type BindToType(string assemblyName, string typeName)
			{
				//NOTE: アセンブリから直接タイプ名を取得する
				typeName = typeName.Replace("DefinedCommand", "BuiltInCommand");

				Type ty = Assembly.GetExecutingAssembly().GetType(typeName);
				//DebugHelpers.Echo(ty +"_ "+ typeName +"_ "+ assemblyName);

				//StringBuilder buf = new StringBuilder();
				//foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
				//{
				//    buf.Append(asm.FullName);
				//    buf.Append("\r\n");
				//}
				//DebugHelpers.Echo(buf);
				return ty;
			}
		}

		public static event EventHandler ExpressionsChanged;

		private static List<GestureExpression> s_exprs = new List<GestureExpression>();
		private static Dictionary<Action, GestureDescription> s_descriptions = new Dictionary<Action, GestureDescription>();

		static GestureSettings()
		{
			var empty = GestureExpression.EmptyTrails;
			string generic = "全般";
			string navigation = "ナビゲーション";
			string tab = "タブ";
			string window = "ウィンドウ";
			string settings = "設定";
			string tabSort = "タブのソート";
			//string file = "ファイル";

			AddDefault(new GestureExpression("L", Action.From(Commands.GoBack, 1)), new GestureDescription(navigation, false));
			AddDefault(new GestureExpression("R", Action.From(Commands.GoForward, 1)), new GestureDescription(navigation, false));
			AddDefault(new GestureExpression("DU", Action.From(Commands.GoUpOneLevel)), new GestureDescription(navigation, false));
			AddDefault(new GestureExpression("UD", Action.From(Commands.RefreshBrowser)), new GestureDescription(generic, false));
			AddDefault(new GestureExpression("D", Action.From(Commands.CloseCurrentTab)), new GestureDescription(tab, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.CloseLeft)), new GestureDescription(tab, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.CloseRight)), new GestureDescription(tab, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.CloseAllButCurrent)), new GestureDescription(tab, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.CloseWindow)), new GestureDescription(window, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.UndoClose)), new GestureDescription(tab, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.ToggleTopMost)), new GestureDescription(generic, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.OpenTabBarOptionDialog)), new GestureDescription(settings, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.OpenButtonBarOptionDialog)), new GestureDescription(settings, false));
			AddDefault(new GestureExpression("UR", new Actions.NextTab()), new GestureDescription(navigation, false));
			AddDefault(new GestureExpression("UL", new Actions.PreviousTab()), new GestureDescription(navigation, false));
			AddDefault(new GestureExpression(empty, new Actions.IsolateTab()), new GestureDescription(tab, false));
			AddDefault(new GestureExpression(empty, new Actions.MergeWindows()), new GestureDescription(window, false));
			AddDefault(new GestureExpression(empty, new Actions.ShowGestureSettings()), new GestureDescription(settings, false));
			AddDefault(new GestureExpression(empty, new Actions.NewTab()), new GestureDescription(tab, true));
			AddDefault(new GestureExpression(empty, new Actions.NewWindow()), new GestureDescription(window, true));
			//AddDefault(new GestureExpression(empty, new Actions.SendKeys("コピー", "^C")), new GestureDescription(file, true));
			//AddDefault(new GestureExpression(empty, new Actions.SendKeys("切り取り", "^X")), new GestureDescription(file, true));
			//AddDefault(new GestureExpression(empty, new Actions.FileOperation("copy")), new GestureDescription(file, true));
			//AddDefault(new GestureExpression(empty, new Actions.FileOperation("cut")), new GestureDescription(file, true));
			//AddDefault(new GestureExpression(empty, new Actions.FileOperation("paste")), new GestureDescription(file, true));
			AddDefault(new GestureExpression(empty, new Actions.ShowWindow(Actions.ShowWindow.SW_MAXIMIZE)), new GestureDescription(window, false));
			AddDefault(new GestureExpression(empty, new Actions.ShowWindow(Actions.ShowWindow.SW_MINIMIZE)), new GestureDescription(window, false));
			AddDefault(new GestureExpression(empty, new Actions.ShowWindow(Actions.ShowWindow.SW_RESTORE)), new GestureDescription(window, false));
			//AddDefault(new GestureExpression(empty, new Actions.CreateProcess("","")), new GestureDescription(generic, true));
			//AddDefault(new GestureExpression(empty,
			//    new Actions.ShellBrowserAccelerator(Actions.ShellBrowserAccelerator.Accelerators.Copy)),
			//    new GestureDescription(generic, true));
			AddDefault(new GestureExpression(empty, Action.From(Commands.BrowseFolder)), new GestureDescription(tab, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.MD5)), new GestureDescription(generic, true));
			AddDefault(new GestureExpression(empty, Action.From(Commands.ReorderTabsByName)), new GestureDescription(tabSort, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.ReorderTabsByPath)), new GestureDescription(tabSort, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.ReorderTabsByActv)), new GestureDescription(tabSort, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.ReorderTabsRevers)), new GestureDescription(tabSort, false));
		}

		private static void AddDefault(GestureExpression expr, GestureDescription desc)
		{
			s_exprs.Add(expr);
			s_descriptions.Add(expr.Action, desc);
		}

		public static GestureDescription GetDescription(Action action)
		{
			GestureDescription desc = null;
			s_descriptions.TryGetValue(action, out desc);
			return desc;
		}

		public static bool IsAcceptTarget(Action action)
		{
			if (action is CustomAction)
			{
				return ((CustomAction)action).IsAcceptTarget;
			}
			else
			{
				GestureDescription desc = GetDescription(action);
				if (desc == null)
				{
					Dbg.Abort("desc == null");
				}
				return desc.IsAcceptTarget;
			}
		}

		public static string GetCategory(Action action)
		{
			if (action is CustomAction)
			{
				return "ユーザー定義";
			}
			else
			{
				GestureDescription desc = GetDescription(action);
				if (desc == null)
				{
					Dbg.Abort("desc == null");
				}
				return desc.Category;
			}
		}

		public static GestureExpression FindExpression(IEnumerable<GestureExpression> list, Action action)
		{
			foreach (GestureExpression expr in list)
			{
				if (expr.Action.Equals(action))
				{
					return expr;
				}
			}
			return null;
		}

		public static GestureExpression FindExpression(IEnumerable<GestureExpression> list, int[] trails)
		{
			foreach (GestureExpression expr in list)
			{
				if (expr.TrailsEquals(trails))
				{
					return expr;
				}
			}
			return null;
		}

		public static IList<GestureExpression> GetDefaultExpressions()
		{
			return s_exprs.AsReadOnly();
		}

		public static IList<GestureExpression> GetAvailableExpressions(IList<GestureExpression> src)
		{
			List<GestureExpression> list = new List<GestureExpression>(src);
			list.RemoveAll((Predicate<GestureExpression>)
				delegate(GestureExpression expr)
				{
					if (expr.Trails == null)
					{
						return true;
					}
					else if (expr.Trails.Length == 0)
					{
						return true;
					}
					else if (expr.Action == null)
					{
						return true;
					}
					return false;
				});
			return list;
		}

		public static IList<GestureExpression> OverrideExpressions(IEnumerable<GestureExpression> list, IEnumerable<GestureExpression> src)
		{
			List<GestureExpression> temp = new List<GestureExpression>(src);
			List<GestureExpression> dest = new List<GestureExpression>();
			foreach (GestureExpression expr in list)
			{
				GestureExpression srcExpr = FindExpression(src, expr.Action);
				dest.Add((srcExpr != null) ? srcExpr : expr);
				temp.Remove(srcExpr);
			}
			dest.AddRange(temp);
			return dest;
		}

		public static IList<GestureExpression> LoadAvailableExpressions()
		{
			List<GestureExpression> src = new List<GestureExpression>();
			LoadExpressions(src);
			IList<GestureExpression> list = GestureSettings.OverrideExpressions(
				GestureSettings.GetDefaultExpressions(), src);
			return GestureSettings.GetAvailableExpressions(list);
		}

		public static void LoadExpressions(ICollection<GestureExpression> list)
		{
			RegistryKey key = PluginInformation.OpenRegistryKey(false);
			if (key == null)
			{
				return;
			}

			byte[] bytes = (byte[])key.GetValue("Expressions");
			if (bytes == null)
			{
				return;
			}
			MemoryStream ms = new MemoryStream(bytes);
			LoadExpressions(list, ms);
		}

		public static void LoadExpressions(ICollection<GestureExpression> list, Stream istream)
		{
			DeflateStream inflate = new DeflateStream(istream, CompressionMode.Decompress);
			BinaryFormatter bin = new BinaryFormatter();
			bin.Binder = new WorkaroundSerializationBinder();
			ExpressionArchiveHeader header = (ExpressionArchiveHeader)bin.Deserialize(inflate);
			if (header.Version != ARCHIVE_VERSION)
			{
				throw new ArchiveVersionMismatchException();
			}
			for (int i = 0; i < header.ObjectCount; i++)
			{
				GestureExpression expr = (GestureExpression)bin.Deserialize(inflate);
				list.Add(expr);
			}
		}

		public static void SaveExpressions(ICollection<GestureExpression> list)
		{
			RegistryKey key = PluginInformation.OpenRegistryKey(true);
			MemoryStream ms = new MemoryStream();
			SaveExpressions(list, ms);
			key.SetValue("Expressions", ms.ToArray(), RegistryValueKind.Binary);
			key.Flush();

			if (ExpressionsChanged != null)
			{
				ExpressionsChanged.Invoke(null, new EventArgs());
			}
		}

		public static void SaveExpressions(ICollection<GestureExpression> list, Stream ostream)
		{
			DeflateStream deflate = new DeflateStream(ostream, CompressionMode.Compress, true);
			BinaryFormatter bin = new BinaryFormatter();

			ExpressionArchiveHeader header = new ExpressionArchiveHeader();
			header.Version = ARCHIVE_VERSION;
			header.ObjectCount = list.Count;
			bin.Serialize(deflate, header);

			foreach (GestureExpression expr in list)
			{
				bin.Serialize(deflate, expr);
			}
			deflate.Flush();	// フラッシュして閉じないと読み込みで失敗する
			deflate.Close();	//（エンドマークが書き込まれない）
		}

		public static void DumpExpressions(ICollection<GestureExpression> list)
		{
			MemoryStream ms = new MemoryStream();
			SaveExpressions(list, ms);
			ms.Seek(0, SeekOrigin.Begin);
			DeflateStream inflate = new DeflateStream(ms, CompressionMode.Decompress);
			string dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().ManifestModule.FullyQualifiedName);
			Stream os = new FileStream(Path.Combine(dir, "expr.bin"), FileMode.Create);
			int c;
			while ((c = inflate.ReadByte()) != -1)
			{
				os.WriteByte((byte)c);
			}
			os.Close();
		}
	}
}
